/**
 * 
 */
package bpel.parser;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.Iterator;

import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.ElementHandler;
import org.dom4j.ElementPath;
import org.dom4j.io.SAXReader;

import wsdl.WSDLParser;
import bpel.BPELImport;
import bpel.BPELProcess;
import bpel.instructions.BPELAssign;
import bpel.instructions.BPELCase;
import bpel.instructions.BPELCondition;
import bpel.instructions.BPELCopy;
import bpel.instructions.BPELFrom;
import bpel.instructions.BPELInvoke;
import bpel.instructions.BPELSwitch;
import bpel.instructions.BPELTo;
import bpel.types.BPELLiteral;

/**
 * @author Maurice & Topia
 * 
 */
public class BPELParser {

	/* Nodes */
	public static final String NODE_PROCESS = "process";
	public static final String NODE_IMPORT = "import";
	public static final String NODE_VARIABLE_LIST = "variables";
	public static final String NODE_VARIABLE = "variable";
	public static final String NODE_ASSIGN = "assign";
	public static final String NODE_COPY = "copy";
	public static final String NODE_FROM = "from";
	public static final String NODE_TO = "to";
	public static final String NODE_LITERAL = "literal";
	public static final String NODE_SWITCH = "switch";
	public static final String NODE_CASE = "case";
	public static final String NODE_INVOKE = "invoke";

	/* Attributes */
	public static final String ATTR_NAME = "name";
	public static final String ATTR_IMPORT_TYPE = "importType";
	public static final String ATTR_LOCATION = "location";
	public static final String ATTR_NAMESPACE = "namespace";
	public static final String ATTR_TARGET_NAMESPACE = "targetNamespace";
	public static final String ATTR_TYPE = "type";
	public static final String ATTR_MSG_TYPE = "messageType";
	public static final String ATTR_VARIABLE = "variable";
	public static final String ATTR_QUERY = "query";
	public static final String ATTR_COND = "condition";
	public static final String ATTR_PART = "part";
	public static final String ATTR_INPUT_VAR = "inputVariable";
	public static final String ATTR_OUTPUT_VAR = "outputVariable";
	public static final String ATTR_OPERATION = "operation";
	public static final String ATTR_PARTNER_LINK = "partnerLink";
	public static final String ATTR_PORT_TYPE = "portType";

	/* Handlers */
	private ElementHandler pruner;
	private ElementHandler handlerProcess;
	private ElementHandler handlerImport;
	private ElementHandler handlerVariables;

	/* Result */
	private BPELProcess process;

	public BPELParser() {

		pruner = new ElementHandler() {
			public void onStart(ElementPath path) {
				/* nothing */
			}

			public void onEnd(ElementPath path) {
				Element row = path.getCurrent();
				// Pruning
				row.detach();
			}
		};

		handlerImport = new ElementHandler() {
			public void onStart(ElementPath path) {
				/* nothing */
			}

			public void onEnd(ElementPath path) {
				Element row = path.getCurrent();
				try {
					parseImport(row);
				} catch (DocumentException e) {
					e.printStackTrace();
				}

				// Pruning
				row.detach();
			}
		};

		handlerProcess = new ElementHandler() {
			public void onStart(ElementPath path) {
				/* nothing */
			}

			public void onEnd(ElementPath path) {
				Element row = path.getCurrent();
				try {
					parseProcess(row);
				} catch (MalformedURLException e) {
					e.printStackTrace();
				}

				// Pruning
				row.detach();
			}
		};

		handlerVariables = new ElementHandler() {
			public void onStart(ElementPath path) {
				/* nothing */
			}

			public void onEnd(ElementPath path) {
				Element row = path.getCurrent();
				parseVariableList(row);

				// Pruning
				row.detach();
			}
		};
	}

	public BPELProcess parse(String BPELFile) throws DocumentException {

		process = new BPELProcess();

		SAXReader saxReader = new SAXReader();

		saxReader.addHandler("/" + NODE_PROCESS, handlerProcess);
		saxReader.addHandler("/" + NODE_PROCESS + "/" + NODE_IMPORT, handlerImport);
		saxReader.addHandler("/" + NODE_PROCESS + "/" + NODE_VARIABLE_LIST, handlerVariables);

		saxReader.setDefaultHandler(pruner);

		saxReader.read(BPELFile);
		
		//DEBUG
		System.out.println(process);
		//DEBUG

		return process;
	}

	@SuppressWarnings("unchecked")
	private void parseProcess(Element processElem) throws MalformedURLException {
		process.setName(processElem.attributeValue(ATTR_NAME));
		process.setTargetNamespace(new URL(processElem.attributeValue(ATTR_TARGET_NAMESPACE)));

		for (Iterator i = processElem.elementIterator(); i.hasNext();) {

			Element element = (Element) i.next();

			if (element.getName().toLowerCase().equals(NODE_ASSIGN)) {
				process.addInstruction(parseAssign(element));
			}

			if (element.getName().toLowerCase().equals(NODE_INVOKE)) {
				process.addInstruction(parseInvoke(element));
			}

			if (element.getName().toLowerCase().equals(NODE_SWITCH)) {
				process.addInstruction(parseSwitch(element));
			}
		}
	}

	private void parseImport(Element importElem) throws DocumentException {
		String importType = importElem.attributeValue(ATTR_IMPORT_TYPE);
		String location = importElem.attributeValue(ATTR_LOCATION);
		String namespace = importElem.attributeValue(ATTR_NAMESPACE);

		process.addImport(new BPELImport(importType, location, namespace));

		if (location != null) {
			WSDLParser wp = new WSDLParser(process);
			wp.parse(location);
		}
	}

	@SuppressWarnings("unchecked")
	private void parseVariableList(Element varListElem) {
		for (Iterator i = varListElem.elementIterator(); i.hasNext();) {
			Element element = (Element) i.next();

			if (element.getName().toLowerCase().equals(NODE_VARIABLE)) {
				parseVariable(element);
			}
		}

	}

	private void parseVariable(Element varElem) {
		String name = varElem.attributeValue(ATTR_NAME);
		String type = varElem.attributeValue(ATTR_TYPE);
		String messageType = varElem.attributeValue(ATTR_MSG_TYPE);

		String notNullType = (type != null ? type : messageType);

		process.addVariable(name, notNullType);
	}

	@SuppressWarnings("unchecked")
	private BPELAssign parseAssign(Element assignElem) {

		BPELAssign assign = new BPELAssign();

		for (Iterator i = assignElem.elementIterator(); i.hasNext();) {
			Element element = (Element) i.next();

			if (element.getName().toLowerCase().equals(NODE_COPY)) {
				assign.addInstruction(parseCopy(element));
			}

			if (element.getName().toLowerCase().equals(NODE_INVOKE)) {
				assign.addInstruction(parseInvoke(element));
			}
		}

		return assign;
	}

	@SuppressWarnings("unchecked")
	private BPELCopy parseCopy(Element copyElem) {

		BPELCopy copy = new BPELCopy();

		for (Iterator i = copyElem.elementIterator(); i.hasNext();) {
			Element element = (Element) i.next();

			if (element.getName().toLowerCase().equals(NODE_FROM)) {
				copy.setFrom(parseFrom(element));
			}

			if (element.getName().toLowerCase().equals(NODE_TO)) {
				copy.setTo(parseTo(element));
			}
		}

		return copy;
	}

	@SuppressWarnings("unchecked")
	private BPELFrom parseFrom(Element fromElement) {
		BPELLiteral literal = null;
		String variable;
		String part;
		String query;

		variable = fromElement.attributeValue(ATTR_VARIABLE);
		part = fromElement.attributeValue(ATTR_PART);
		query = fromElement.attributeValue(ATTR_QUERY);

		for (Iterator i = fromElement.elementIterator(); i.hasNext();) {
			Element element = (Element) i.next();

			if (element.getName().toLowerCase().equals(NODE_LITERAL)) {
				literal = new BPELLiteral(element.getStringValue());
			}
		}

		return new BPELFrom(literal, variable, part, query);
	}

	@SuppressWarnings("unchecked")
	private BPELTo parseTo(Element toElement) {
		BPELLiteral literal = null;
		String variable;
		String part;
		String query;

		variable = toElement.attributeValue(ATTR_VARIABLE);
		part = toElement.attributeValue(ATTR_PART);
		query = toElement.attributeValue(ATTR_QUERY);

		for (Iterator i = toElement.elementIterator(); i.hasNext();) {
			Element element = (Element) i.next();

			if (element.getName().toLowerCase().equals(NODE_LITERAL)) {
				literal = new BPELLiteral(element.getStringValue());
			}
		}

		return new BPELTo(literal, variable, part, query);
	}

	private BPELInvoke parseInvoke(Element invokeElem) {
		String name = invokeElem.attributeValue(ATTR_NAME);
		String partnerLink = invokeElem.attributeValue(ATTR_PARTNER_LINK);
		String portType = invokeElem.attributeValue(ATTR_PORT_TYPE);
		String operation = invokeElem.attributeValue(ATTR_OPERATION);
		String inputVariable = invokeElem.attributeValue(ATTR_INPUT_VAR);
		String outputVariable = invokeElem.attributeValue(ATTR_OUTPUT_VAR);

		return new BPELInvoke(name, partnerLink, portType, operation, inputVariable, outputVariable);
	}

	@SuppressWarnings("unchecked")
	private BPELSwitch parseSwitch(Element switchElem) {

		BPELSwitch s = new BPELSwitch();

		for (Iterator i = switchElem.elementIterator(); i.hasNext();) {
			Element element = (Element) i.next();

			if (element.getName().toLowerCase().equals(NODE_CASE)) {
				s.addCase(parseCase(element));
			}
		}

		return s;
	}

	@SuppressWarnings("unchecked")
	private BPELCase parseCase(Element caseElem) {
		// TODO
		BPELCase c = new BPELCase();
		c.setCondition(parseCondition(caseElem.attributeValue(ATTR_COND)));

		for (Iterator i = caseElem.elementIterator(); i.hasNext();) {
			Element element = (Element) i.next();

			if (element.getName().toLowerCase().equals(NODE_ASSIGN)) {
				c.addInstruction(parseAssign(element));
			}

			if (element.getName().toLowerCase().equals(NODE_INVOKE)) {
				c.addInstruction(parseInvoke(element));
			}
		}

		return c;
	}

	private BPELCondition parseCondition(String condition) {
		return new BPELCondition(condition);
	}
}
